//
//  LXApp.swift
//  LXExtensions
//
//  Created by yoctech on 2021/8/13.
//

import Foundation
import UIKit


@objc public class LXApp: NSObject {
    //MARK:- 屏幕信息

    @objc public static let screenBounds = UIScreen.main.bounds
    //屏幕大小
    @objc public static let screenSize                           = screenBounds.size
    //屏幕宽度
    @objc public static let screenWidth:CGFloat                  = screenSize.width
    //屏幕高度
    @objc public static let screenHeight:CGFloat                 = screenSize.height

    //状态栏默认高度
    @objc public static var statusBarHeight:CGFloat {
        get {
            if #available(iOS 13.0, *) {
                if let statusBarManager = UIApplication.shared.windows.first?.windowScene?.statusBarManager {
                return statusBarManager.statusBarFrame.size.height
                }else{
                    return isFullScreen ? 44: 20
                }
           }
           else {
            return UIApplication.shared.statusBarFrame.height
           }
        }
    }
    //导航栏默认高度
    @objc public static let navBarHeight:CGFloat = statusBarHeight + 44
    //Tabbar默认高度
    @objc public static let tabBarHeight:CGFloat = (screenHeight > 811 ? 83.0 : 49.0)

    @objc public static let screenWidthRatio:CGFloat =  screenWidth / 375;
    @objc public static let screenHeightRatio:CGFloat = screenHeight / 667;

    //刘海屏
    @objc public static var isFullScreen: Bool {
        if #available(iOS 11, *) {
            guard let w = UIApplication.shared.delegate?.window, let unwrapedWindow = w else {
                  return false
              }
              
              if unwrapedWindow.safeAreaInsets.left > 0 || unwrapedWindow.safeAreaInsets.bottom > 0 {
                  print(unwrapedWindow.safeAreaInsets)
                  return true
              }
        }
        return false
    }
    //MARK:- APP信息
    
    @objc public static let infoDictionary            = Bundle.main.infoDictionary
    @objc public static let appName: String?         = infoDictionary!["CFBundleDisplayName"] as? String
    @objc public static let appVersion: String?      = infoDictionary!["CFBundleShortVersionString"] as? String
    @objc public static let appBuildVersion: String? = infoDictionary!["CFBundleVersion"] as? String
    @objc public static let bundleIdentifier: String? = infoDictionary!["CFBundleIdentifier"] as? String
    @objc public static let IDFV: String? = UIDevice.current.identifierForVendor?.uuidString
    
    /// 判断是否是调试模式
    @objc public static var isBeingDebugged: Bool {
        // Initialize all the fields so that,
        // if sysctl fails for some bizarre reason, we get a predictable result.
        var info = kinfo_proc()
        // Initialize mib, which tells sysctl the info we want,
        // in this case we're looking for info about a specific process ID.
        var mib = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
        // Call sysctl.
        var size = MemoryLayout.stride(ofValue: info)
        let junk = sysctl(&mib, u_int(mib.count), &info, &size, nil, 0)
        assert(junk == 0, "sysctl failed")
        // We're being debugged if the P_TRACED flag is set.
        return (info.kp_proc.p_flag & P_TRACED) != 0
    }

    //MARK:- 系统信息
    
    /// iphone 用户名
    @objc public static let iPhoneName: String     = UIDevice.current.name
    /// 平台信息 iPhone, iPad, iPhoneSimulator...
    @objc public static let platformName: String?     = infoDictionary!["DTPlatformName"] as? String

    /// 手机型号-直出; eg.  iPhone13,1
    @objc public static var modelName: String {
        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.reduce("") { identifier, element in
            guard let value = element.value as? Int8, value != 0 else { return identifier }
            return identifier + String(UnicodeScalar(UInt8(value)))
        }
        return identifier
    }
    
    /// 手机型号-转换成市场型号, eg.  iPhone 12 mini; 如果出了新设备但是没更新,就直接返回modelName iPhone13,1
    @objc public static var modelDeviceName: String {
        switch modelName {
        case "iPod1,1":  return "iPod Touch 1"
        case "iPod2,1":  return "iPod Touch 2"
        case "iPod3,1":  return "iPod Touch 3"
        case "iPod4,1":  return "iPod Touch 4"
        case "iPod5,1":  return "iPod Touch (5 Gen)"
        case "iPod7,1":   return "iPod Touch 6"
        case "iPhone3,1", "iPhone3,2", "iPhone3,3":  return "iPhone 4"
        case "iPhone4,1":  return "iPhone 4s"
        case "iPhone5,1":   return "iPhone 5"
        case  "iPhone5,2":  return "iPhone 5 (GSM+CDMA)"
        case "iPhone5,3":  return "iPhone 5c (GSM)"
        case "iPhone5,4":  return "iPhone 5c (GSM+CDMA)"
        case "iPhone6,1":  return "iPhone 5s (GSM)"
        case "iPhone6,2":  return "iPhone 5s (GSM+CDMA)"
        case "iPhone7,2":  return "iPhone 6"
        case "iPhone7,1":  return "iPhone 6 Plus"
        case "iPhone8,1":  return "iPhone 6s"
        case "iPhone8,2":  return "iPhone 6s Plus"
        case "iPhone8,4":  return "iPhone SE"
        case "iPhone9,1":   return "国行、日版、港行iPhone 7"
        case "iPhone9,2":  return "港行、国行iPhone 7 Plus"
        case "iPhone9,3":  return "美版、台版iPhone 7"
        case "iPhone9,4":  return "美版、台版iPhone 7 Plus"
        case "iPhone10,1","iPhone10,4":   return "iPhone 8"
        case "iPhone10,2","iPhone10,5":   return "iPhone 8 Plus"
        case "iPhone10,3","iPhone10,6":   return "iPhone X"
        case "iPhone11,8":  return "iPhone XR"
        case "iPhone11,2":  return "iPhone XS"
        case "iPhone11,6":  return "iPhone XS Max"
        case "iPhone11,4":  return "iPhone XS Max"
        case "iPhone12,1":  return "iPhone 11"
        case "iPhone12,3":  return "iPhone 11 Pro"
        case "iPhone12,5":  return "iPhone 11 Pro Max"
        case "iPhone12,8":  return "iPhone SE (2nd generation)"
        case "iPhone13,1":  return "iPhone 12 mini"
        case "iPhone13,2":  return "iPhone 12"
        case "iPhone13,3":  return "iPhone 12 Pro"
        case "iPhone13,4":  return "iPhone 12 Pro Max"
        case "iPad1,1":   return "iPad"
        case "iPad1,2":   return "iPad 3G"
        case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":   return "iPad 2"
        case "iPad2,5", "iPad2,6", "iPad2,7":  return "iPad Mini"
        case "iPad3,1", "iPad3,2", "iPad3,3":  return "iPad 3"
        case "iPad3,4", "iPad3,5", "iPad3,6":   return "iPad 4"
        case "iPad4,1", "iPad4,2", "iPad4,3":   return "iPad Air"
        case "iPad4,4", "iPad4,5", "iPad4,6":  return "iPad Mini 2"
        case "iPad4,7", "iPad4,8", "iPad4,9":  return "iPad Mini 3"
        case "iPad5,1", "iPad5,2":  return "iPad Mini 4"
        case "iPad5,3", "iPad5,4":   return "iPad Air 2"
        case "iPad6,3", "iPad6,4":  return "iPad Pro 9.7"
        case "iPad6,7", "iPad6,8":  return "iPad Pro 12.9"
        case "AppleTV2,1":  return "Apple TV 2"
        case "AppleTV3,1","AppleTV3,2":  return "Apple TV 3"
        case "AppleTV5,3":   return "Apple TV 4"
        case "i386", "x86_64":   return "Simulator"
        default:  return modelName
        }
    }
        
    /// 获取当前设备局域网IP
    @objc public static var deviceIP: String {
        var addresses = [String]()
        var ifaddr : UnsafeMutablePointer<ifaddrs>? = nil
        if getifaddrs(&ifaddr) == 0 {
            var ptr = ifaddr
            while (ptr != nil) {
                let flags = Int32(ptr!.pointee.ifa_flags)
                var addr = ptr!.pointee.ifa_addr.pointee
                if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
                    if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) {
                        var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                        if (getnameinfo(&addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),nil, socklen_t(0), NI_NUMERICHOST) == 0) {
                            if let address = String(validatingUTF8:hostname) {
                                addresses.append(address)
                            }
                        }
                    }
                }
                ptr = ptr!.pointee.ifa_next
            }
            freeifaddrs(ifaddr)
        }
        if let ipStr = addresses.first {
            return ipStr
        } else {
            return ""
        }
    }
    
    /// 🔋电池电量, 0~1.0 , 获取不到就是-1.0
    @objc public static var batteryLevel: CGFloat {
        if !UIDevice.current.isBatteryMonitoringEnabled {
            UIDevice.current.isBatteryMonitoringEnabled = true
        }
        return CGFloat(UIDevice.current.batteryLevel)
    }
    /// 🔋电池状态
    @objc public static var batteryState: UIDevice.BatteryState {
        if !UIDevice.current.isBatteryMonitoringEnabled {
            UIDevice.current.isBatteryMonitoringEnabled = true
        }
        return UIDevice.current.batteryState
    }
    
    //MARK:- 越狱检测
    
    /// 判断是否越狱
    @objc public static var isJailbroken: Bool {
        return jailbreakFileExists
            || sandboxBreached
            || evidenceOfSymbolLinking
    }

    @objc static private var evidenceOfSymbolLinking: Bool {
        var s = stat()
        guard lstat("/Applications", &s) == 0 else { return false }
        return (s.st_mode & S_IFLNK == S_IFLNK)
    }

    @objc static private var sandboxBreached: Bool {
        guard (try? " ".write(
            toFile: "/private/jailbreak.txt",
            atomically: true, encoding: .utf8)) == nil else {
                return true
        }
        return false
    }

    @objc static private var jailbreakFileExists: Bool {
        let jailbreakFilePaths = [
            "/Applications/Cydia.app",
            "/Library/MobileSubstrate/MobileSubstrate.dylib",
            "/bin/bash",
            "/usr/sbin/sshd",
            "/etc/apt",
            "/private/var/lib/apt/"
        ]
        let fileManager = FileManager.default
        return jailbreakFilePaths.contains { path in
            if fileManager.fileExists(atPath: path) {
                return true
            }
            if let file = fopen(path, "r") {
                fclose(file)
                return true
            }
            return false
        }
    }
    
    //MARK:- 性能检测
    
    /// 内存占用信息(已用,总共)
    @objc public static var memorySize: LXUsageInfo {
        var taskInfo = task_vm_info_data_t()
        var count = mach_msg_type_number_t(MemoryLayout<task_vm_info>.size) / 4
        let result: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
            $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
                task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
            }
        }
        var used: UInt64 = 0
        if result == KERN_SUCCESS {
            used = UInt64(taskInfo.phys_footprint)
        }
        let total = ProcessInfo.processInfo.physicalMemory
        return LXUsageInfo(used: used, total: total)
    }
        
    /// 磁盘占用信息(已用,总共)
    @objc public static var diskSize: LXUsageInfo {
        if #available(iOS 11.0, *) {
            let fileURL = URL(fileURLWithPath: NSHomeDirectory() as String)
            var used: UInt64 = 0
            var total: UInt64 = 0
            var free: UInt64 = 0
            do {
                let freeValues = try fileURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
                if let capacity = freeValues.volumeAvailableCapacityForImportantUsage {
                    free = UInt64(capacity)
                }
                let totalValues = try fileURL.resourceValues(forKeys: [.volumeTotalCapacityKey])
                if let capacity = totalValues.volumeTotalCapacity {
                    total = UInt64(capacity)
                }
                used = total - free
            } catch {
            }
            return LXUsageInfo(used: used, total: total)
        }
        //iOS11以下获取的已使用磁盘存储空间和手机显示的不符
        do {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
            if let path = paths.last {
                let dictionary = try FileManager.default.attributesOfFileSystem(forPath: path)
                let total = UInt64(dictionary[FileAttributeKey.systemSize] as? Float ?? 0)
                let free = UInt64(dictionary[FileAttributeKey.systemFreeSize] as? Float ?? 0)
                
                return LXUsageInfo(used: total - free, total: total)
            }
        } catch {
        }
        return LXUsageInfo(used: 0, total: 0)
    }
    
    /// 将磁盘或者内存的size转换成字符串输出
    /// - Parameters:
    ///   - fileSize: size大小
    ///   - unit: 转换进制,经测试磁盘是1KB=1000B,内存是1KB=1024B
    /// - Returns: 带单位的字符串
    @objc public static func sizeToString(fileSize:UInt64, unit: Float = 1000) -> String {
         
         let fileSize1 = Float(fileSize)
         
         let KB:Float = unit
         let MB:Float = KB*KB
         let GB:Float = MB*KB
         
         if fileSize < 10 {
             return "0 B"
             
         } else if fileSize1 < KB {
             return "< 1 KB"
         } else if fileSize1 < MB {
             return String(format: "%.1f KB", Float(fileSize1)/KB)
         } else if fileSize1 < GB {
             return String(format: "%.1f MB", Float(fileSize1)/MB)
         } else {
             return String(format: "%.1f GB", Float(fileSize1)/GB)
         }
     }
    
    /// CPU使用率
    @objc public static var cpuUsage: Double {
        var totalUsageOfCPU: Double = 0.0
        var threadsList: thread_act_array_t?
        var threadsCount = mach_msg_type_number_t(0)
        let threadsResult = withUnsafeMutablePointer(to: &threadsList) {
            return $0.withMemoryRebound(to: thread_act_array_t?.self, capacity: 1) {
                task_threads(mach_task_self_, $0, &threadsCount)
            }
        }
        if threadsResult == KERN_SUCCESS, let threadsList = threadsList {
            for index in 0..<threadsCount {
                var threadInfo = thread_basic_info()
                var threadInfoCount = mach_msg_type_number_t(THREAD_INFO_MAX)
                let infoResult = withUnsafeMutablePointer(to: &threadInfo) {
                    $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
                        thread_info(threadsList[Int(index)], thread_flavor_t(THREAD_BASIC_INFO), $0, &threadInfoCount)
                    }
                }
                
                guard infoResult == KERN_SUCCESS else {
                    break
                }
                
                let threadBasicInfo = threadInfo as thread_basic_info
                if threadBasicInfo.flags & TH_FLAGS_IDLE == 0 {
                    totalUsageOfCPU = (totalUsageOfCPU + (Double(threadBasicInfo.cpu_usage) / Double(TH_USAGE_SCALE) * 100.0))
                }
            }
        }
        
        vm_deallocate(mach_task_self_, vm_address_t(UInt(bitPattern: threadsList)), vm_size_t(Int(threadsCount) * MemoryLayout<thread_t>.stride))
        return totalUsageOfCPU
    }
    
    
    //MARK: - app启动
    
    /// app启动时调用,会把当前版本启动次数+1
    @objc public static func appLaunch() {
        let userDefault = UserDefaults.standard
        let appLaunchCount = userDefault.integer(forKey: "LXApp.appLaunchCount")
        userDefault.set(appLaunchCount + 1, forKey: "LXApp.appLaunchCount")

        guard let appVersion = appVersion else { return }
        
        let appLaunchVersion = userDefault.string(forKey: "LXApp.appLaunchVersion")
        if appLaunchVersion != appVersion {
            userDefault.set(appVersion, forKey: "LXApp.appLaunchVersion")
            userDefault.set(1, forKey: "LXApp.appVersionLaunchCount")
        }else{
            let appVersionLaunchCount = userDefault.integer(forKey: "LXApp.appVersionLaunchCount")
            userDefault.set(appVersionLaunchCount + 1, forKey: "LXApp.appVersionLaunchCount")
        }
        userDefault.synchronize()
    }
    
    /// app是否首次启动,使用此判断前需要调用
    @objc public static var appIsFirstLaunch: Bool {
        return appLaunchCount <= 1
    }
    
    @objc public static var appVersionIsFirstLaunch: Bool {
        return appVersionLaunchCount <= 1
    }
    
    /// app启动次数
    @objc public static var appLaunchCount: Int {
        let count = UserDefaults.standard.integer(forKey: "LXApp.appLaunchCount")
        if count == 0 {
            fatalError("需要在app启动时调用LXApp.appLaunch()才能使用appLaunchCount")
        }
        return count
    }
    /// app当前版本启动次数
    @objc public static var appVersionLaunchCount: Int {
        guard let appVersion = appVersion else {
            fatalError("LXApp获取不到当前版本号,无法使用appVersionIsFirstLaunch")
        }
        let userDefault = UserDefaults.standard

        let appLaunchVersion = userDefault.string(forKey: "LXApp.appLaunchVersion")
        if appLaunchVersion != appVersion {
            return 0
        }else{
            let appVersionLaunchCount = userDefault.integer(forKey: "LXApp.appVersionLaunchCount")
            if appVersionLaunchCount == 0 {
                fatalError("需要在app启动时调用LXApp.appLaunch()才能使用appVersionIsFirstLaunch")
            }
            return appVersionLaunchCount
        }
    }

}
